#include "alarm_clock.h"

/* Variables declaration ******************************************************/

type_timeRTC *time;
type_timeRTC *alarm;
Event *event;
Fsm * fsm;

/* Functions implementation ***************************************************/

void alarmClock_init(Fsm* me, State init) {
	// init hardware
	lcd_init();
	lcd_clear();
	lcd_setCursor(1, 0);
	fprintf(lcdout, "HH : MM");
	lcd_setCursor(1, 1);
	fprintf(lcdout, "set time hours    ");
	led_allInit();
	buzzer_init();
	button_init();

	// init fsm
	fsm = me;
	time = malloc(sizeof(type_timeRTC));
	time->hour = 0;
	time->minute = 0;
	time->second = 0;
	time->milli = 0;
	alarm = malloc(sizeof(type_timeRTC));
	alarm->hour = 0;
	alarm->minute = 0;
	alarm->second = 0;
	alarm->milli = 0;
	event = malloc(sizeof(Event));
	fsm_init(me, init);
}

void alarmClock_set_hours(Fsm *me, const Event *e) {
	switch (e->signal) {
	case ROTARY:
		//change time or alarm ?
		if (me->alarmSet == ALARM_ON) {
			alarm->hour++;
			alarm->hour %= DAY;
			lcd_setCursor(1, 0);
			fprintf(lcdout, "%02i : MM  ", alarm->hour);
		} else if (me->alarmSet == ALARM_OFF) {
			time->hour++;
			time->hour %= DAY;
			lcd_setCursor(1, 0);
			fprintf(lcdout, "%02i : MM  ", time->hour);
			scheduler_setTimeRTC(time);
		}
		break;
	case JOYSTICK:
		me->state = alarmClock_set_minutes;
		if (me->alarmSet == ALARM_ON) {
			lcd_setCursor(1, 0);
			fprintf(lcdout, "%02i : %02i  ", alarm->hour, alarm->minute);
			lcd_setCursor(1, 1);
			fprintf(lcdout, "set alarm minutes");
		} else if (me->alarmSet == ALARM_OFF) {
			lcd_setCursor(1, 0);
			fprintf(lcdout, "%02i : %02i  ", time->hour, time->minute);
			lcd_setCursor(1, 1);
			fprintf(lcdout, "set time minutes  ");
			scheduler_setTimeRTC(time);
		}
		break;
	default:
		break;
	}
}

void alarmClock_set_minutes(Fsm *me, const Event *e) {
	switch (e->signal) {
	case ROTARY:
		//change time or alarm ?
		if (me->alarmSet == ALARM_ON) {
			alarm->minute++;
			alarm->minute %= HOUR;
			lcd_setCursor(1, 0);
			fprintf(lcdout, "%02i : %02i  ", alarm->hour, alarm->minute);
		} else if (me->alarmSet == ALARM_OFF) {
			time->minute++;
			time->minute %= HOUR;
			lcd_setCursor(1, 0);
			fprintf(lcdout, "%02i : %02i  ", time->hour, time->minute);
		}
		break;
	case JOYSTICK:
		if (me->alarmSet == ALARM_ON) {
			me->state = alarmClock_alarm_on;
			lcd_setCursor(1, 1);
			fprintf(lcdout, "alarm set at %02i:%02i", alarm->hour, alarm->minute);
			scheduler_add(match_alarm_clock, IMMEDIATELY);
		} else {
			me->state = alarmClock_alarm_off;
			me->alarmSet = ALARM_OFF;
			lcd_setCursor(1, 1);
			fprintf(lcdout, "alarm off             ");
			time->second = 0;
			scheduler_setTimeRTC(time);
		}
		display_clock();
		toggle_green_led();
		break;
	default:
		break;
	}
}

void alarmClock_alarm_off(Fsm *me, const Event *e) {
	switch (e->signal) {
	case ROTARY:
		me->state = alarmClock_alarm_on;
		led_yellowOn();
		me->alarmSet = ALARM_ON;
		lcd_setCursor(1, 1);
		fprintf(lcdout, "alarm set at %02i:%02i ", alarm->hour, alarm->minute);
		scheduler_add(match_alarm_clock, IMMEDIATELY);
		break;
	case JOYSTICK:
		me->state = alarmClock_set_hours;
		scheduler_remove(display_clock);
		scheduler_remove(toggle_green_led);
		lcd_setCursor(1, 0);
		fprintf(lcdout, "HH : MM     ");
		lcd_setCursor(1, 1);
		fprintf(lcdout, "set time hours     ");
		break;
	default:
		break;
	}
}

void alarmClock_alarm_on(Fsm *me, const Event *e) {
	switch (e->signal) {
	case ROTARY:
		lcd_setCursor(1, 1);
		fprintf(lcdout, "alarm off                ");
		me->state = alarmClock_alarm_off;
		me->alarmSet = ALARM_OFF;
		led_yellowOff();
		break;
	case JOYSTICK:
		me->state = alarmClock_set_hours;
		scheduler_remove(display_clock);
		scheduler_remove(toggle_green_led);
		lcd_setCursor(1, 0);
		fprintf(lcdout, "HH : MM     ");
		lcd_setCursor(1, 1);
		fprintf(lcdout, "set alarm hours    ");
		break;
	case SYSTEM_TIME_MATCHES:
		led_redOn();
		toggle_buzz();
		lcd_setCursor(2, 2);
		fprintf(lcdout, "wake up! :-)");
		scheduler_add(set_alarm_off, ALARM_TIMEOUT);
		me->state = alarmClock_buzzing;
		break;
	default:
		break;
	}
	scheduler_remove(match_alarm_clock);
}

void alarmClock_buzzing(Fsm *me, const Event *e) {
	switch (e->signal) {
	case JOYSTICK:
		set_alarm_off();
		break;
	case ROTARY:
		set_alarm_off();
		break;
	default:
		break;
	}
}

inline static void fsm_init(Fsm* me, State init) {
	me->state = init;
	me->timeSet = 0; // used to save the alarm clock
	me->alarmSet = ALARM_OFF; // alarm mode is off
	event->signal = INIT;
	fsm_dispatching();
}

inline static void fsm_dispatch(Fsm* me, const Event* e) {
	if (e->signal != NONE) {
		me->state(me, e);
		event->signal = NONE;
	}
}

void fsm_dispatching() {
	fsm_dispatch(fsm, event);
	scheduler_add(fsm_dispatching, MILLISECOND);
}

void display_clock() {
	scheduler_add(display_clock, ONE_SECOND);
	scheduler_getTimeRTC(time);
	lcd_setCursor(1, 0);
	fprintf(lcdout, "%02i : %02i : %02i ", time->hour, time->minute, time->second);
}

void match_alarm_clock() {
	type_timeRTC * tempTime = malloc(sizeof(type_timeRTC));
	scheduler_getTimeRTC(tempTime);
	if (tempTime->hour == alarm->hour && tempTime->minute == alarm->minute)
		event->signal = SYSTEM_TIME_MATCHES;
	else
		scheduler_add(match_alarm_clock, ONE_SECOND);
	free(tempTime);
}

void set_event_signal(uint8_t signal) {
	event->signal = signal;
}

void set_alarm_off() {
	end_buzz();
	scheduler_remove(set_alarm_off);
	scheduler_remove(toggle_buzz);
	fsm->state = alarmClock_alarm_off;
	fsm->alarmSet = ALARM_OFF;
	led_redOff();
	led_yellowOff();
	lcd_setCursor(1, 1);
	fprintf(lcdout, "alarm off          ");
	lcd_setCursor(2, 2);
	fprintf(lcdout, "            ");
}

void toggle_buzz() {
	start_buzz();
	scheduler_add(end_buzz, 500);
	scheduler_add(toggle_buzz,  ONE_SECOND);
}

void toggle_green_led() {
	led_greenToggle();
	scheduler_add(toggle_green_led, ONE_SECOND);
}

uint32_t calculate_miliseconds(const type_timeRTC *time) {
	uint32_t tempTime = 0;
	tempTime += time->minute * MILLI_TO_MIN;
	tempTime += time->hour * MILLI_TO_HOUR;
	return tempTime;
}
